#include "PhysicsCommon.h"
#include <stdlib.h>
//Global Variables
void (*user_display_callback)(float t, float dt) = 0;
void (*user_input_callback)(unsigned char key, int x, int y) = 0;
float current_time = 0;
float _simSpeed = 1.0f;
float DELTA_TIME = 0.1f;
int _width = 640, _height = 480;
bool _fullscreen = false;
PhysicsCamera _camera;
const GLfloat AmbientLight[] = { 0.5f, 0.5f, 0.5f, 1.0f };
const GLfloat DiffuseLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat DiffusePosition[] = { 0.0f, 100.0f, -100.0f, 1.0f };
//Internal hidden function prototypes
void drawString(const char* str,int x, int y);	//Should be exposed later on...
//PhysicsEngine namespace implementations
int PhysicsEngine::PhysicsInit(int &argc, char** argv){
	glutInit(&argc, argv);
	glutInitWindowSize(_width, _height);
	glutInitWindowPosition(10,10);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("SimSynergy");
	glutReshapeFunc(PhysicsEngine::PhysicsResize);
	glutDisplayFunc(PhysicsEngine::PhysicsDisplay);
	glutIdleFunc(PhysicsEngine::PhysicsIdle);
	glutKeyboardFunc(PhysicsEngine::PhysicsInput);
	glutSpecialFunc(PhysicsEngine::PhysicsSpecialInput);
	glutMotionFunc(PhysicsEngine::PhysicsMouseMotion);
	glClearColor(0,0,0,0);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glClearDepth(1.0f);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glLightfv(GL_LIGHT1, GL_AMBIENT, AmbientLight);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, DiffuseLight);
	glLightfv(GL_LIGHT1, GL_POSITION, DiffusePosition);
	glEnable(GL_LIGHT1);
	_camera.position[2] = 10;
	return 0;
}
void PhysicsEngine::RunSimulation(void (*callback) (float, float)){
	user_display_callback = callback;
	glutMainLoop();
}
void PhysicsEngine::PhysicsResize(int width, int height){
	if(height==0) height = 1;
	_width = width; _height = height;
	const float ar = (float) width / (float) height;
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f,ar,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}
void PhysicsEngine::PhysicsDisplay(){
	static int fps_draw = 1;
	static float _fps_time = 0, _fps_start_time = 0;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);	//Camera stuff.  Not yet implemented
	glLoadIdentity();
	glViewport(0,0,_width,_height);
	gluPerspective(45,_width/_height,0,100);
	gluLookAt(_camera.position[0],_camera.position[1],_camera.position[2],
			_camera.viewVector[0],_camera.viewVector[1],-_camera.position[2],0,1,0);
	glMatrixMode(GL_MODELVIEW);
	unsigned long long t = glutGet(GLUT_ELAPSED_TIME);
	if(user_display_callback!=0)
		user_display_callback(current_time, ((float) t)*_simSpeed/1000.0f - current_time);
	PhysicsEngine::AdvanceSimulation(t);
	char buffer[50];
	sprintf(buffer,"Time: %.3f seconds FPS: %.3f",current_time, 200.0f/_fps_time);
	drawString(buffer,10,10);
	fps_draw = (fps_draw+1)%200;
	if(fps_draw==0){
		_fps_time = current_time/ _simSpeed - _fps_start_time;
		_fps_start_time = current_time / _simSpeed;
	}
	glutSwapBuffers();
	glFlush();	//Let OpenGL finish rendering the scene
}
void drawString(const char* str,int x, int y){	//This function is hidden from the end user until better implemented and a place is found.
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, (GLfloat)_width, 0.0f, (GLfloat)_height, 0, 1);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	int current_position = x;
	int len = strlen(str);
	for(int i=0;i<len;i++){
		glLoadIdentity();
		glRasterPos3f(current_position,y,0);
		current_position += glutBitmapWidth(GLUT_BITMAP_HELVETICA_10,str[i]) + 1;
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10,str[i]);
	}
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glFrustum(-_width/_height, _width/_height, -1.0, 1.0, 2.0, 100.0);
	glLoadIdentity();
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}
void PhysicsEngine::PhysicsIdle(){	//Maybe more to come
	PhysicsEngine::PhysicsDisplay();
}
void PhysicsEngine::PhysicsSpecialInput(int key, int x, int y){
	switch(key){
		case GLUT_KEY_F1:
			_fullscreen ? glutFullScreen() : glutReshapeWindow(640,480);
			_fullscreen = !_fullscreen;
			break;
		default:
			break;
	}
}
void PhysicsEngine::PhysicsInput(unsigned char key, int x, int y){
	switch(key){
		case '`': 
			return;	//Console window goes here.
		case 27 : 
			exit(0);
			break;
		default:
			if(user_input_callback!=0)
				user_input_callback(key,x,y);
			break;
	}
	PhysicsEngine::PhysicsDisplay();
}
void PhysicsEngine::PhysicsMouseMotion(int x, int y){
	_camera.viewVector = PhysicsVector((float)(x - _width/2)*-0.01f, ((float)y - _height/2)*0.01f, 0);
	PhysicsEngine::PhysicsDisplay();
}
void PhysicsEngine::SetInputCallback(void (*callback) (unsigned char key, int x, int y)){
	user_input_callback = callback;
}
void PhysicsEngine::AdvanceSimulation(unsigned long long t){
	static float accumulator = 0;
	float dt = ((float) t)*_simSpeed/1000.0f - current_time;
	accumulator += dt;
	std::list<PhysicsObject*> objects = PhysicsEngine::getObjects();
	for(;accumulator>=DELTA_TIME;accumulator-=DELTA_TIME,current_time+=DELTA_TIME){
		for(list<PhysicsObject*>::iterator i = objects.begin(); i != objects.end(); i++)
			(*i)->update(current_time,DELTA_TIME);
	}
	for(list<PhysicsObject*>::iterator i = objects.begin(); i != objects.end(); i++)
		(*i)->render();
}
float PhysicsEngine::GetCurrentTime(){	//in seconds
	return current_time;
}
PhysicsCamera& PhysicsEngine::GetCamera(){
	return _camera;
}
void PhysicsEngine::SetSimSpeed(float s){
	_simSpeed = s;
	DELTA_TIME = (s > 1.0f ? 0.1f : s / 100.0f);
}
